#!/usr/bin/python

import re, sys, zlib
import lzw
import cStringIO
from pyparsing import nestedExpr
from pyparsing import ParserElement
from pyparsing import Regex
from pyparsing import Empty


class pdfobj:
    def __init__(self,keynum,data):
        print "\n"
        print "keynum: \n",keynum
        print "object data: \n",data
        print "\n"
        self.tags = {} #tuples of [key,value]
        self.keynum = keynum
        self.indata = data
        self.tagstream = ''
        self.hiddenTags = 0 #tags containing non-normalized data
        self.staticScript = '' #for those things not within objects append to this structure

        #special children types
        self.original_stream = ""

        if isinstance(data,list):
           self.tags = self.parse_dictionary(data)

        if isinstance(data,str):
           self.parseObject()

    def __repr__(self):
        out = 'pdfobj %s\n' % (self.keynum)
        return out

    def parseObject(self):
        tags = re.match('^(<<.*>>)[\s\r\n%]*(?:stream[\s\r\n]*(.*?)\n?endstream)?$',self.indata,re.DOTALL)        
        if tags:
            tag,stream = tags.group(1), tags.group(2)
            tag = re.sub("^%.*\n$","",tag,re.MULTILINE) #handle comment
            if stream:
               self.original_stream = stream            
            print "in line 63, tag is:\n%s "%tag
            ParserElement.setDefaultWhitespaceChars('')   #this step is very important 
            cascaded_list = nestedExpr("<<", ">>",None,Regex(r"ignoreExpr - expression for ignoring opening and closing delimiters (default=quotedString)")).parseString(tag).asList()
            print "\n\nlen(cascaded_list):%d\n\n"%len(cascaded_list)
            print "cascaded_list:\n",cascaded_list
            if len(cascaded_list)==1 and isinstance(cascaded_list[0], list):
               cascaded_list = cascaded_list[0]
               print "\n\nlen(cascaded_list):%d\n\n"%len(cascaded_list)
            else: 
                print "return in line 71" 
                return
            self.tags = self.parse_dictionary(cascaded_list)



    def parse_dictionary(self,cascaded_list):
            key_value_dictionary = {}
            #############################################################
            #should put the following in a self-defined dictionary object
            #############################################################
            state = 0    #0 means original, should get the key first; 1 means / in the [] or (), should value += value; 2 means the value is a name object,3 means the value is a dictionary
            for i in range (0,len(cascaded_list)):                
                cascaded_list_element = cascaded_list[i]
                print "\n\nthe %d cascaded_list_element:\n%s"%(i,cascaded_list_element) 
                if isinstance(cascaded_list_element, str) and state == 0:  #every string cascaded_list_element must begin with a key, so the state must be 0
                   print "\n\nif isinstance(cascaded_list_element, str) and state == 0:\n\n"
                   splited_list = cascaded_list_element.split('/')
                   print "len(splited_list):",len(splited_list)
                   print "splited_list:\n",splited_list
                   for j in range (0,len(splited_list)):                           
                           splited_list_element = splited_list[j]
                           print "\n\nthe %d splited_list_element:\n%s"%(j,splited_list_element)
                           if  splited_list_element is '':
                               continue                           
                           
                           if state ==2 : # this name object is the value of the former key
                              is_a_name = re.match('^([^\s\(\)<>\[\]{}/%]+)\s*$',splited_list_element)
                              if is_a_name :
                                 name_value =  is_a_name.group(1)
                                 key_value_dictionary[pdfobj.fixPound(key)] = ["name obj",pdfobj.fixPound(name_value)]
                                 state = 0
                                 print "in line 83, key_value_dictionary[pdfobj.fixPound(key)] = [\"name obj\",pdfobj.fixPound(name_value)]:\npdfobj.fixPound(key):%s \n type: name obj\n  pdfobj.fixPound(name_value):%s"%(pdfobj.fixPound(key),pdfobj.fixPound(name_value))
                                 print "in line 100, set state = 0"
                                 continue

                           if state ==1:
                              value_with_slash = value_with_slash + '/'+splited_list_element
                              print "in line 112, state 1, value_with_slash = value_with_slash + '/'+splited_list_element"
                              print "value_with_slash:",value_with_slash
                              if re.search('[^\%s]*\%s\s*$'%(match_end_delimiter,match_end_delimiter),str(value_with_slash)):
                                 print "value_with_slash = value_with_slash + '/'+splited_list_element, match in the end"
                                 if delimiter_type == "literal string":
                                       if re.match('^(\\\\[0-9]{3}\s*)+$',value_with_slash): #ie. need to convert \040 == 0x20
                                          value_with_slash = re.sub('\\\\([0-9]{3})', lambda mo: chr(int(mo.group(1),8)), value_with_slash)
                                       value_with_slash = re.sub('\\\\n','\n', value_with_slash)
                                       value_with_slash = re.sub('\\\\t','\t', value_with_slash)
                                       value_with_slash = re.sub('\\\\(.)','\\1', value_with_slash)    
                                       value_with_slash = get_bracketed_content(value_with_slash)                                
                                 key_value_dictionary[pdfobj.fixPound(key)] = [ delimiter_type ,value_with_slash]
                                 state = 0
                                 print "in line 108, key_value_dictionary[pdfobj.fixPound(key)] = [ delimiter_type ,value_with_slash]:\npdfobj.fixPound(key):%s \n type: %s\n  value:%s"%(pdfobj.fixPound(key),delimiter_type,value_with_slash)
                                 print "in line 109, set state = 0"
                                 continue
                           
                           if state==0:
                              indirect_refer = re.match('^([^\s\(\)<>\[\]{}/%]+)\s+(\d+\s+\d+)\s+R\s*$',splited_list_element)
                              if  indirect_refer :   
                                  key = indirect_refer.group(1)
                                  refer_value = indirect_refer.group(2)
                                  key_value_dictionary[pdfobj.fixPound(key)] = ["refer" ,refer_value]
                                  print "in line 119, key_value_dictionary[pdfobj.fixPound(key)] = [\"refer\" ,refer_value]:\npdfobj.fixPound(key):%s \n type: refer\n  value:%s"%(pdfobj.fixPound(key),refer_value)
                                  print "in line 99,  state is still 0"
                                  continue 

                              number_value = re.match('^([^\s\(\)<>\[\]{}/%]+)\s+(\d+.*)\s*$',splited_list_element)
                              if  number_value :   
                                  key = number_value.group(1)
                                  number = number_value.group(2)
                                  key_value_dictionary[pdfobj.fixPound(key)] = ["number" ,number]
                                  print "in line 134, key_value_dictionary[pdfobj.fixPound(key)] = [\"number\" ,number]:\npdfobj.fixPound(key):%s \n type: number\n  value:%s"%(pdfobj.fixPound(key),number)
                                  print "in line 134,  state is still 0"
                                  continue                  

                              both_key_value = re.match('^([^\s\(\)<>\[\]{}/%]+)\s*([\(\)<>\[\]{}])',splited_list_element)# the key and the value are both in the split fragment
                              if both_key_value:
                                 print "both_key_value is True"
                                 key = both_key_value.group(1)
                                 match_start_character = both_key_value.group(2)
                                 if match_start_character is '(':
                                    print "match_start_character is '(':"
                                    match_end_delimiter =')'
                                    delimiter_type = "literal string"
                                    oneliner = re.match('^([^\s\(\)<>\[\]{}/%]+)\s*(\(.*\))\s*$',splited_list_element,re.DOTALL)
                                    if oneliner:
                                       value  =  oneliner.group(2)
                                       if re.match('^(\\\\[0-9]{3}\s*)+$',value): #ie. need to convert \040 == 0x20
                                          value = re.sub('\\\\([0-9]{3})', lambda mo: chr(int(mo.group(1),8)), value)
                                       value = re.sub('\\\\n','\n', value)
                                       value = re.sub('\\\\t','\t', value)
                                       value = re.sub('\\\\(.)','\\1', value)
                                       value = get_bracketed_content(value)
                                       key_value_dictionary[pdfobj.fixPound(key)] = [delimiter_type,value]
                                       print "in line 134, key_value_dictionary[pdfobj.fixPound(key)] = [delimiter_type,value]:\npdfobj.fixPound(key):%s \n type: %s\n  value:%s"%(pdfobj.fixPound(key),delimiter_type,value)
                                       print "in line 112,  state is still 0"
                                    else:                 #there may be a slash in the ()
                                       multi_liner = re.match('^([^\s\(\)<>\[\]{}/%]+)\s*(\(.*)$',splited_list_element,re.DOTALL)
                                       value_with_slash = multi_liner.group(2)
                                       state = 1
                                       print "in line 136, set state = 1"
                                 
                                 elif match_start_character is '[':  
                                    print "match_start_character is '[':  "
                                    match_end_delimiter =']'
                                    delimiter_type = "array"
                                    oneliner = re.match('^([^\s\(\)<>\[\]{}/%]+)\s*(\[.*\])\s*$',splited_list_element,re.DOTALL)
                                    if oneliner:
                                       value  =  oneliner.group(2)
                                       key_value_dictionary[pdfobj.fixPound(key)] = [delimiter_type ,value]
                                       print "in line 149, key_value_dictionary[pdfobj.fixPound(key)] = [delimiter_type,value]:\npdfobj.fixPound(key):%s \n type: %s\n  value:%s"%(pdfobj.fixPound(key),delimiter_type,value)
                                       print "in line 124,  state is still 0"
                                    else:   #there may be a slash in the []
                                       multi_liner = re.match('^([^\s\(\)<>\[\]{}/%]+)\s*(\[.*)$',splited_list_element,re.DOTALL)
                                       value_with_slash = multi_liner.group(2)
                                       state = 1
                                       print "in line 149, set state = 1"
                                 else:
                                    print "both key and value, but not ( or ["
                                    value = re.match('^([^\s\(\)<>\[\]{}/%]+)\s*([\(\)<>\[\]{}].*)$',splited_list_element,re.DOTALL).group(2)
                                    key_value_dictionary[pdfobj.fixPound(key)] = ["unknown" ,value]
                                    print "in line 134, key_value_dictionary[fpdfobj.fixPound(key)] = [\"unknown\",value]:\npdfobj.fixPound(key):%s \n type: %s\n  value:%s"%(pdfobj.fixPound(key),"unknown",value)
                                    print "in line 132,  state is still 0"
                                       



                              else:
                                  onlykey = re.match('^([^\s\(\)<>\[\]{}/%]+)\s*$',splited_list_element)# only the key are in the fragment, this means the value of the key is a name object(the next splited_list_element) or a dictionary(the next cascaded_list_element)
                                  if onlykey:
                                     key = onlykey.group(1)
                                     if j== len(splited_list)-1:   #the value of the key is  a dictionary(the next cascaded_list_element)  
                                         state = 3
                                         print "in line 164, set state = 3"
                                     else:
                                         state = 2             #the value of the key is a name object(the next splited_list_element)
                                         print "in line 167, set state = 2"        

                                  else: 
                                      print "return in line 168"
                                      return {}

                 
                elif isinstance(cascaded_list_element, list) and state == 3 :#the cascaded dictionary must be a value,so the state must be 2
                     print "\n\nelif isinstance(cascaded_list_element, list) and state == 3 :\n\n"
                     dictionary_value  = pdfobj("direct_dictionary_obj",cascaded_list_element)
                     key_value_dictionary[pdfobj.fixPound(key)] = [ "dictionary obj",dictionary_value]
                     print "in line 194, key_value_dictionary[pdfobj.fixPound(key)] = [ \"dictionary obj\",dictionary_value]:\nkey:%s   value:%s"%(key,dictionary_value)
                     state = 0
                     print "in line 196, set state = 0"
                     
                else: 
                    print "return in line 180"
                    return {}
            return key_value_dictionary


    def decode_stream(self): 
        if not self.original_stream:    # this object doesn't have stream
           print "in line 221,return \"\""
           return ""

        if "Filter" not in self.tags.keys():     # the stream doesn't have filters
              print "no filters, in line 227,return original_stream"
              return  self.original_stream

        else:           #the stream have filters, then do the following process 
          filter_list = []    
          stream = self.original_stream
          if self.tags["Filter"][0]  == "name obj":
              filter_list = [pdfobj.fixPound(self.tags["Filter"][1])]
          elif self.tags["Filter"][0]  == "array":           
              filters =  re.findall("/([^\s\(\)<>\[\]{}/%]+)",self.tags["Filter"][1])
              for filtername in filters :
                  filter_list.append(pdfobj.fixPound(filtername))
          else:
              print "in line 236,return" 
              return ""

          for  k in filter_list:
                    if k == 'FlateDecode' or k == 'Fl': 
                        print 'indirect obj %s has flatedecode stream' % (self.keynum)
                        try:
                           stream  = zlib.decompress(stream)
                        except zlib.error, msg:
                            if pdf.DEBUG:
                                print 'failed to decompress object %s' % (self.keynum)
                                return ''
                        #print 'indirect obj %s after decoding, stream is %s' % (key,stream)

                    elif k == 'ASCIIHexDecode' or k == 'AHx':
                        print 'indirect obj %s has ASCIIHexDecode stream' % (self.keynum)
                        result = ''
                        counter = 0
                        stream = re.sub('[^a-fA-F0-9]+','',stream)
                        for i in range(0,len(stream),2):
                            result += chr(int('0x'+stream[i:i+2],0))
                        stream = result
                        #print 'indirect obj %s after decoding, stream is %s' % (key,stream)
                    
                    elif k == 'ASCII85Decode' or k == 'A85':
                        print 'indirect obj %s has ASCII85Decode stream' % (self.keynum)
                        stream = pdfobj.ascii85(stream)
                        #print 'indirect obj %s after decoding, stream is %s' % (key,stream)
                    
                    elif k == 'LZWDecode' or k == 'LZW':
                        print 'indirect obj %s has LZWDecode stream' % (self.keynum)
                        stream = pdfobj.lzwdecode(stream)
                        #print 'indirect obj %s after decoding, stream is %s' % (key,stream)
                    
                    elif k == 'RunLengthDecode' or k == 'RL':
                        print 'indirect obj %s has RunLengthDecode stream' % (self.keynum)
                        stream = pdfobj.rldecode(stream)
                        #print 'indirect obj %s after decoding, stream is %s' % (key,stream)
                    
                    else: 
                        print "can't handle this filter,return \"\""
                        return ""
          
          return stream          


    def obj_check_jbig2(self): 
        if not self.original_stream:    # this object doesn't have stream
           print "this object doesn't have stream,return False"
           return False

        if "Filter" not in self.tags.keys():     # the stream doesn't have filters
              print "the stream doesn't have filters, return False"
              return  False

        else:           #the stream have filters, then do the following process 
          filter_list = []    
          stream = self.original_stream
          if self.tags["Filter"][0]  == "name obj":
              filter_list = [pdfobj.fixPound(self.tags["Filter"][1])]
          elif self.tags["Filter"][0]  == "array":           
              filters =  re.findall("/([^\s\(\)<>\[\]{}/%]+)",self.tags["Filter"][1])
              for filtername in filters :
                  filter_list.append(pdfobj.fixPound(filtername))
          else:
              print "in line 294,return flase" 
              return False
          
          if "JBIG2Decode" in filter_list:
              for  k in filter_list:
                    if k == 'FlateDecode' or k == 'Fl': 
                        print 'indirect obj %s has flatedecode stream' % (self.keynum)
                        try:
                           stream  = zlib.decompress(stream)
                        except zlib.error, msg:
                            if pdf.DEBUG:
                                print 'failed to decompress object %s' % (self.keynum)
                                return False
                        #print 'indirect obj %s after decoding, stream is %s' % (key,stream)

                    elif k == 'ASCIIHexDecode' or k == 'AHx':
                        print 'indirect obj %s has ASCIIHexDecode stream' % (self.keynum)
                        result = ''
                        counter = 0
                        stream = re.sub('[^a-fA-F0-9]+','',stream)
                        for i in range(0,len(stream),2):
                            result += chr(int('0x'+stream[i:i+2],0))
                        stream = result
                        #print 'indirect obj %s after decoding, stream is %s' % (key,stream)
                    
                    elif k == 'ASCII85Decode' or k == 'A85':
                        print 'indirect obj %s has ASCII85Decode stream' % (self.keynum)
                        stream = pdfobj.ascii85(stream)
                        #print 'indirect obj %s after decoding, stream is %s' % (key,stream)
                    
                    elif k == 'LZWDecode' or k == 'LZW':
                        print 'indirect obj %s has LZWDecode stream' % (self.keynum)
                        stream = pdfobj.lzwdecode(stream)
                        #print 'indirect obj %s after decoding, stream is %s' % (key,stream)
                    
                    elif k == 'RunLengthDecode' or k == 'RL':
                        print 'indirect obj %s has RunLengthDecode stream' % (self.keynum)
                        stream = pdfobj.rldecode(stream)
                        #print 'indirect obj %s after decoding, stream is %s' % (key,stream)
                                        
                    elif k == 'JBIG2Decode' :
                         print 'indirect obj %s has JBIG2Decode stream' % (self.keynum)
                         return check_jbig2_stream(stream)
                    else: 
                        print "can't handle this filter,return \"\""
                        return False
          
          return False   

    @staticmethod
    def fixPound(i):
        #returns '#3a' substituted with ':', etc
        #strips newlines, '[', and ']' characters
        #this allows indexing in arrays

        i = re.sub('[\[\]\n]','',i)
        i = re.sub('<<$','',i)
        return re.sub('#([a-fA-F0-9]{2})', lambda mo: chr(int('0x' + mo.group(1), 0)), i)
        
    @staticmethod
    def lzwdecode(input):
        try:
            return ''.join(lzw.LZWDecoder(cStringIO.StringIO(input)).run())
        except:
            return input

    @staticmethod
    def rldecode(input):
        output = ''
        index = 0
        try:
            key_len = ord(input[index])

            while key_len != 0x80:
                index += 1
                if key_len & 0x80:
                    output += input[index] * (256 - key_len + 1)
                    index += 1
                else:
                    output += input[index:index+key_len + 1]
                    index += key_len + 1
                key_len = ord(input[index])
        except: 
            return input
        return output

    @staticmethod
    def ascii85(input):
        outdata = ''
        input = re.sub('\s','',input)
        input = re.sub('^<~','',input)
        input = re.sub('~>$','',input)

        for i in range(0,len(input),5):
            bytes = input[i:i+5]
            fraglen = len(bytes)
            if bytes[0] == 'z':
                pass #ignore
            if bytes[0] == 'y':
                pass #ignoredictionary obj
            if i+5 >= len(input):
                #data not divisible by 5
                bytes = input[i:]
                fraglen = len(bytes)
                if fraglen>1:
                    bytes += 'vvv'

            total = 0
            shift = 85*85*85*85
            for c in bytes:
                total += shift*(ord(c)-33)
                shift /= 85

            if fraglen > 1:
                outdata += chr((total>>24) % 256)
                if fraglen > 2:
                    outdata += chr((total>>16) % 256)
                    if fraglen > 3:
                        outdata += chr((total>>8) % 256)
                        if fraglen > 4:
                            outdata += chr((total) % 256)
        return outdata


def get_bracketed_content(str_with_brackets):
    is_bracketed_content = re.match('^\((.*)\)$',str_with_brackets,re.DOTALL)
    if is_bracketed_content :
       return is_bracketed_content.group(1)
    else:return str_with_brackets

def check_jbig2_stream(jbig2_stream):
    int_value = ord(jbig2_stream[4])
    #print "int_value:", int_value
    if (int_value&0x40==0x40) and (int_value&0x3f==0 or int_value&0x3f==4 or int_value&0x3f==6 or int_value&0x3f==7): 
        return True
    else:
       print "the value in jbig2_stream are not secsitive value, return Flase" 
       return False
